/*
 * Copyright (c) 2010 Mathias Garbe <code@mathias-garbe.de>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "Update.hpp"

namespace
{
    // Used by writeToMemoryCallback
    struct MemoryChunk {
        char *data;
        size_t size;
    };

    // This is the file write callback for curls download
    size_t writeToFileCallback(void *ptr, size_t size, size_t nmemb, FILE *stream) {
        size_t written;
        written = fwrite(ptr, size, nmemb, stream);
        return written;
    }

    // This is the memory write callback for curls download
    static size_t writeToMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
    {
        size_t realsize = size * nmemb;
        struct MemoryChunk *mem = (struct MemoryChunk *)data;

        mem->data = (char*)realloc(mem->data, mem->size + realsize + 1);
        if (mem->data == NULL) {
            /* out of memory! */ 
            printf("not enough memory (realloc returned NULL)\n");
            exit(EXIT_FAILURE);
        }

        memcpy(&(mem->data[mem->size]), ptr, realsize);
        mem->size += realsize;
        mem->data[mem->size] = 0;

        return realsize;
    }

    // This is the download progress callback
    int progressCallback(void *ptr, double t, /* dltotal */double d, /* dlnow */ double ultotal,double ulnow)
    {
        #ifdef _DEBUG
            std::cout << "Progress: " << (d*100.0/t) << "%" << std::endl;
        #endif

        Update* update = Update::instance();
        if(update == 0)
            throw std::runtime_error("Update instance got deleted but is still downloading.");

        if(update->downloadAborted)
        {
            return -1;
        }

        // WinAPI message "loop"
        if(PeekMessage(&update->Msg, NULL, 0, 0, 1) > 0)
        {
            TranslateMessage(&update->Msg);
            DispatchMessage(&update->Msg);
        }

        int percent = (d*100.0/t);
        std::stringstream sstream;
        sstream << "File progress: " << percent << "%";

        SendMessage(update->progressBar, PBM_SETPOS, (WPARAM)(d*100.0/t), 0); 
        SendMessage(update->progressLabel, WM_SETTEXT, NULL, (LPARAM)sstream.str().c_str());

        return 0;
    }

    LRESULT CALLBACK _WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
        return Update::instance()->WndProc(hwnd, msg, wParam, lParam);
    }
}

Update::Update()
{
    downloadAborted = false;

    Update::instance(this);
    this->curl = curl_easy_init();
    if(!this->curl)
        throw std::runtime_error("curl could not be initialized.");

	const char className[] = "UpdateClass";

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = _WndProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = WinAPI::hInstance();
	wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
	wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = className;
	wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

	if(!RegisterClassEx(&wc))
	{
		MessageBox(NULL, "Window Registration Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK);
		return;
	}

	hwnd = CreateWindowEx(WS_EX_WINDOWEDGE, className , "Downloading CnCnet Update...", WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
		WinAPI::desktopWidth()/2 - 300, WinAPI::desktopHeight()/2 - 55, 300, 110, NULL, NULL, WinAPI::hInstance(), NULL);

	if(hwnd == NULL)
	{
		MessageBox(NULL, "Window Creation Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK);
		return;
	}

	ShowWindow(hwnd, SW_SHOWNORMAL);
	UpdateWindow(hwnd);

	struct MemoryChunk chunk;

	chunk.data = (char*)malloc(1);
	chunk.size = 0;

	char *url = "http://cncnet.cnc-comm.com/qtclient/dependencies/dependencies.txt";

	curl_easy_setopt(this->curl, CURLOPT_URL, url);
	curl_easy_setopt(this->curl, CURLOPT_WRITEFUNCTION, writeToMemoryCallback);
	curl_easy_setopt(this->curl, CURLOPT_WRITEDATA, (void *)&chunk);
	curl_easy_setopt(this->curl, CURLOPT_USERAGENT, "CnCNetLegacy");
	curl_easy_setopt(this->curl, CURLOPT_TIMEOUT, 10);

	CURLcode code = curl_easy_perform(this->curl);

	// Well, some kind of error. I don't know. Don't need to.
	// Just return a negative version number.
	if(code != 0)
		return;

	std::vector<std::string> dependencies = std::split(chunk.data, '\n');

	if(chunk.data)
		free(chunk.data);

	for(int i=0; i<dependencies.size(); i++)
	{
		std::string sOut = WinAPI::getPath().append("/").append(dependencies[i]);
		std::cout << sOut << std::endl;
		if(!FileExists(sOut.c_str()))
		{
			// Set window title to dll name
			std::stringstream title;
			title << "File (" << (i+1) << "/" << dependencies.size() << "): " << dependencies[i];
			SendMessage(this->progressFileLabel, WM_SETTEXT, NULL, (LPARAM)title.str().c_str());

			// Set dll url
			std::string sUrl = "http://cncnet.cnc-comm.com/qtclient/dependencies/";
			sUrl.append(dependencies[i]);
			const char *url = sUrl.c_str();

			// Set dll file name and path
			char outfilename[4096];
			memset(outfilename, 0, 4096);
			if(sOut.size() >= 4095)
			{
				throw std::runtime_error("Buffer overflow in Update::Update()");
			}
			strcpy_s(outfilename, 4096, sOut.c_str());
			//strcpy(outfilename, sOut.c_str()); // use strcpy_s?

			// Create directorys, if dll is in subdir
			std::vector<std::string> parts = std::split(dependencies[i], '/');
			std::string path = WinAPI::getPath().append("/");
			for(int p = 0; p<parts.size()-1; p++)
			{
				path.append(parts[p]).append("/");
				CreateDirectoryA(path.c_str(), 0);
				std::cout << path << std::endl;
			}


			FILE* fp = fopen(outfilename, "w+b");

			curl_easy_setopt(this->curl, CURLOPT_URL, url);
			curl_easy_setopt(this->curl, CURLOPT_WRITEFUNCTION, writeToFileCallback);
			curl_easy_setopt(this->curl, CURLOPT_WRITEDATA, fp);
			curl_easy_setopt(this->curl, CURLOPT_NOPROGRESS, 0L);
            curl_easy_setopt(this->curl, CURLOPT_PROGRESSFUNCTION, progressCallback);
            curl_easy_setopt(this->curl, CURLOPT_USERAGENT, "CnCNetLegacy");
			curl_easy_setopt(this->curl, CURLOPT_PROGRESSDATA, 0);
			curl_easy_setopt(this->curl, CURLOPT_TIMEOUT, 0);

			CURLcode code = curl_easy_perform(this->curl);
			fclose(fp);

			if(code != 0)
			{
                std::cout << "error " << code << std::endl;
				DeleteFileA(WinAPI::getPath().append(dependencies[i]).c_str());
				return;
			}

		}
	}



	MoveFile("cncnet.exe", "update.tmp");
	SendMessage(this->progressFileLabel, WM_SETTEXT, NULL, (LPARAM)"Downloading CnCNet v2...");

	char *url2 = "http://cncnet.cnc-comm.com/qtclient/updates/cncnet.exe";

	char outfilename2[4096];
	memset(outfilename2, 0, 4096);
	if(WinAPI::getPath().append("/cncnet.exe").size() >= 4095)
	{
		throw std::runtime_error("Buffer overflow in Update::Update()");
	}
	strcpy(outfilename2, WinAPI::getPath().append("/cncnet.exe").c_str());

	FILE* fp = fopen(outfilename2, "w+b");

	curl_easy_setopt(this->curl, CURLOPT_URL, url2);
	curl_easy_setopt(this->curl, CURLOPT_WRITEFUNCTION, writeToFileCallback);
	curl_easy_setopt(this->curl, CURLOPT_WRITEDATA, fp);
	curl_easy_setopt(this->curl, CURLOPT_NOPROGRESS, 0L);
	curl_easy_setopt(this->curl, CURLOPT_PROGRESSFUNCTION, progressCallback);
	curl_easy_setopt(this->curl, CURLOPT_PROGRESSDATA, 0);
	curl_easy_setopt(this->curl, CURLOPT_TIMEOUT, 0);

	CURLcode code2 = curl_easy_perform(this->curl);
	fclose(fp);

	PROCESS_INFORMATION pInfo;
	STARTUPINFOA sInfo;
	memset(&sInfo, 0, sizeof(STARTUPINFO));
	CreateProcessA(NULL, (LPSTR)WinAPI::getPath().append("/cncnet.exe").c_str(), 0, 0, FALSE, 0, 0, 0, &sInfo, &pInfo);
	exit(0);

}

Update::~Update()
{
    Update::instance(0, true);
    if(this->curl)
        curl_easy_cleanup(this->curl);
}

LRESULT Update::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CREATE:
            {
	            INITCOMMONCONTROLSEX InitCtrlEx;

	            InitCtrlEx.dwSize = sizeof(INITCOMMONCONTROLSEX);
	            InitCtrlEx.dwICC  = ICC_PROGRESS_CLASS;
	            InitCommonControlsEx(&InitCtrlEx);

		        this->progressBar = CreateWindowEx(0, PROGRESS_CLASS, NULL, WS_CHILD | WS_VISIBLE | PBS_SMOOTH, 20, 20, 260, 17, hwnd, NULL, WinAPI::hInstance(), NULL);
                CreateWindowEx(0, PROGRESS_CLASS, NULL, WS_CHILD | WS_VISIBLE | PBS_SMOOTH, 20, 20, 260, 17, hwnd, NULL, WinAPI::hInstance(), NULL);
                SendMessage(this->progressBar, PBM_SETRANGE, 0, MAKELPARAM(0, 100)); 
                SendMessage(this->progressBar, PBM_SETPOS, (WPARAM) 0, 0); 

				this->progressFileLabel = CreateWindow(TEXT("STATIC"), "...", WS_CHILD | WS_VISIBLE | SS_LEFT, 110, 40, 170, 60, hwnd, (HMENU) 1, NULL, NULL);
				this->progressLabel = CreateWindow(TEXT("STATIC"), "File progress: 0%", WS_CHILD | WS_VISIBLE | SS_LEFT, 110, 60, 170, 60, hwnd, (HMENU) 1, NULL, NULL);

                HFONT hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
				SendMessage(this->progressFileLabel, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
				SendMessage(this->progressLabel, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
            }
            break;
        case WM_GETMINMAXINFO:
            {
                MINMAXINFO * mmi = reinterpret_cast<MINMAXINFO *>(lParam);
                mmi->ptMaxSize.x = 123;
                mmi->ptMaxSize.y = 123;
            }
            break;

        case WM_CLOSE:
            {
                int msgboxID = MessageBox(hwnd, "The Auto-Update is currently downloading the latest update.\nDo you want to cancel the download?", "Update", 
                                          MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2);

                if(msgboxID == IDYES)
                {
                    DestroyWindow(hwnd);
                    downloadAborted = true;
                }
            }
            break;

        case WM_DESTROY:
            DestroyWindow(hwnd);
            break;

        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}
